﻿using Google.GData.Documents;
using Google.GData.Client;
using Google.GData.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;

namespace BlogExtractor {
	public partial class ExtractForm : Form {
		const string _urlPattern = "^http://";
		string _blogUrl;

		internal List<Post> Posts { get; set; }

		public ExtractForm() { InitializeComponent(); }

		protected override void OnLoad(EventArgs e) {
			base.OnLoad(e);
			_blogUrl = txtBlogUrl.Text = Program.Profile.BlogUrl;
			cbxUseProxy.Checked = Program.Profile.UseProxy;

			lstFormat.Items.Add(new ExtractFormat(Format.Forum));
			lstFormat.Items.Add(new ExtractFormat(Format.Google));
			lstFormat.ValueMember = "Value";
			lstFormat.DisplayMember = "Name";
			lstFormat.SelectedIndex = 0;

			if (!string.IsNullOrEmpty(_blogUrl)) {
				this.LoadPosts();
			} else {
				this.EditEnabled = true;
				txtBlogUrl.Focus();
			}
		}

		/// <summary>
		/// http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
		/// </summary>
		void GoogleLogin() {
			DocumentsService googleDocs = new DocumentsService("exampleCo-exampleApp-1");
			googleDocs.setUserCredentials("jo@gmail.com", "mypassword");
			//googleDocs.
		}

		Format SelectedFormat {
			get { return ((ExtractFormat)lstFormat.SelectedItem).Format; }
		}

		private bool ValidFeed(string url) {
			return !string.IsNullOrEmpty(url) && Regex.IsMatch(url, _urlPattern);
		}
		private bool ValidFeed() { return this.ValidFeed(txtBlogUrl.Text); }

		private void HandleInvalidFeed() {
			this.EndProgress();
			this.ShowError(txtBlogUrl.Text + " is not a valid blog feed", "Invalid Blog Feed");
			txtBlogUrl.Focus();
			txtBlogUrl.Enabled = true;
		}

		private void HandleError(System.Exception e) {
			this.RunInMainThread(() => {
				this.EndProgress();
				this.ShowError(e.Message, "Error");
			});
		}

		void ShowError(string message, string title) {
			MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
		}

		void ShowProgress(string message) {
			progressBar.Visible = true;
			lblStatus.Text = message + " …";
		}
		void EndProgress() {
			progressBar.Visible = false;
			lblStatus.Text = string.Empty;
		}

		#region Load Post List

		private void LoadPosts() {
			if (this.ValidFeed()) {
				lstPosts.Items.Clear();
				btnExtract.Enabled = btnEditFeed.Enabled = false;
				this.ShowProgress("Loading Posts");
				this.LoadPostsWorker.RunWorkerAsync();
			} else {
				this.HandleInvalidFeed();
			}
		}

		private void LoadPostsWorker_DoWork(object sender, DoWorkEventArgs e) {
			try {
				this.Posts = Post.Load(_blogUrl);
			} catch (System.Exception ex) {
				this.HandleError(ex);
			}
		}

		private void LoadPostsWorker_CompletedWork(object sender, RunWorkerCompletedEventArgs e) {
			this.EndProgress();
			if (this.Posts != null && this.Posts.Count > 0) {
				lstPosts.DataSource = this.Posts;
				lstPosts.ValueMember = "ID";
				lstPosts.DisplayMember = "Title";
				lstPosts.Enabled = true;
				this.EditEnabled = false;
			} else {
				this.EditEnabled = true;
				txtBlogUrl.Focus();
				MessageBox.Show("No posts were found at " + txtBlogUrl.Text, "Invalid Blog Feed");
			}
		}

		#endregion

		#region Extract Post

		private void ExtractionWorker_DoWork(object sender, DoWorkEventArgs e) {
			try {
				string id = this.ValueFromMainThread(() => (string)lstPosts.SelectedValue);
				var post = this.Posts.FirstOrDefault(p => p.ID == id);
				if (post != null) {
					Format f = this.ValueFromMainThread(() => this.SelectedFormat);
					switch (f) {
						case Format.Forum: e.Result = post.ToBBCode(); break;
						case Format.Google: e.Result = post.ToGoogleDoc(); break;
						default:
							this.ShowError("Unable to determine extraction format", "Extraction Error");
							e.Cancel = true;
							break;
					}
				} else {
					this.ShowError("Unable to load content for " + lstPosts.SelectedText, "Extraction Error");
					e.Cancel = true;
				}
			} catch (System.Exception ex) {
				this.HandleError(ex);
				e.Cancel = true;
			}
		}

		private void ConversionWorker_CompletedWork(object sender, RunWorkerCompletedEventArgs e) {
			if (!e.Cancelled) {
				Clipboard.SetData(DataFormats.Text, (string)e.Result);
				btnExtract.Enabled = true;
				this.EndProgress();
				MessageBox.Show("Extracted content has been copied to the clipboard",
					"Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
			}

		}

		#endregion

		#region Events

		/// <summary>
		/// Enable fields for editing and saving and disable other functions
		/// </summary>
		bool EditEnabled {
			set {
				cbxUseProxy.Enabled = txtBlogUrl.Enabled = btnSaveFeed.Enabled = value;
				btnExtract.Enabled = btnEditFeed.Enabled = !value;
			}
		}

		private void btnSaveFeed_Click(object sender, EventArgs e) {
			Program.Profile.UseProxy = cbxUseProxy.Checked;

			if (this.ValidFeed()) {
				this.EditEnabled = false;
				Program.Profile.BlogUrl = txtBlogUrl.Text;
				if (_blogUrl != txtBlogUrl.Text) {
					// reload post list if the feed has changed
					_blogUrl = txtBlogUrl.Text;
					this.LoadPosts();
				}
			} else {
				this.HandleInvalidFeed();
			}
		}

		private void btnEditFeed_Click(object sender, EventArgs e) {
			this.EditEnabled = true;
			txtBlogUrl.Focus();
		}

		private void btnConvert_Click(object sender, EventArgs e) {
			this.ShowProgress("Extracting Post for " + this.SelectedFormat.ToString());
			btnExtract.Enabled = false;
			this.ExtractionWorker.RunWorkerAsync();
		}

		private void txtBlogUrl_TextChanged(object sender, EventArgs e) {
			btnEditFeed.Enabled = Regex.IsMatch(txtBlogUrl.Text, _urlPattern);
		}

		#endregion

		/// <summary>
		/// Ensure operation is run in the UI thread
		/// </summary>
		void RunInMainThread(Action fn) {
			if (this.InvokeRequired) { this.Invoke(fn); } else { fn(); }
		}

		/// <summary>
		/// Run operation in UI thread to return a value
		/// </summary>
		T ValueFromMainThread<T>(Func<T> fn) {
			if (this.InvokeRequired) { return (T)this.Invoke(fn); } else { return fn(); }
		}

		public class ExtractFormat {
			Format _format;
			internal ExtractFormat(Format f) { _format = f; }
			internal Format Format { get { return _format; } }
			public int Value { get { return (int)_format; } }
			public string Name { get { return _format.ToString(); } }
		}
	}
}
